<!DOCTYPE html>
<html lang="zh" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="description" content="book">
<meta name="generator" content="Paradox, paradox-material-theme=0.6.0, mkdocs-material=3.0.3">

<meta name="lang:clipboard.copy" content="Copy to clipboard">
<meta name="lang:clipboard.copied" content="Copied to clipboard">
<meta name="lang:search.language" content="">
<meta name="lang:search.pipeline.stopwords" content="true">
<meta name="lang:search.pipeline.trimmer" content="true">
<meta name="lang:search.result.none" content="No matching documents">
<meta name="lang:search.result.one" content="1 matching document">
<meta name="lang:search.result.other" content="# matching documents">
<meta name="lang:search.tokenizer" content="[\s\-]+">


<meta name="description" content="book">
<link rel="shortcut icon" href="../assets/images/favicon.png">
<title>Akka Typed Actor · Scala Web 开发——基于Akka HTTP</title>
<link rel="stylesheet" href="../assets/stylesheets/application.451f80e5.css">
<link rel="stylesheet" href="../assets/stylesheets/application-palette.22915126.css">
<meta name="theme-color" content="#009688" />
<link rel="stylesheet" href="../lib/material__tabs/dist/mdc.tabs.min.css">
<link rel="stylesheet" href="../lib/prettify/prettify.css">
<script src="../assets/javascripts/modernizr.1aa3b519.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,700|Roboto+Mono">
<style>
body,input{font-family:"Roboto","Helvetica Neue",Helvetica,Arial,sans-serif}
code,kbd,pre{font-family:"Roboto Mono","Courier New",Courier,monospace}
</style>
<link rel="stylesheet" href="../assets/fonts/font-awesome.css">
<link rel="stylesheet" href="../assets/fonts/material-icons.css">
<link rel="stylesheet" href="../assets/stylesheets/paradox-material-theme.css">
</head>
<body
data-md-color-primary="teal"
data-md-color-accent="indigo"
>
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" data-md-component="overlay" for="__drawer"></label>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href="../index.html" title="Scala Web 开发——基于Akka HTTP" class="md-header-nav__button md-logo">
<i class="md-icon">local_library</i>
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="__drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<div class="md-flex__ellipsis md-header-nav__title" data-md-component="title">
<span class="md-header-nav__topic">
Scala Web 开发——基于Akka HTTP
</span>
<span class="md-header-nav__topic">
Akka Typed Actor
</span>
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--search md-header-nav__button" for="__search"></label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query" data-md-state="active">
<label class="md-icon md-search__icon" for="__search"></label>
<button type="reset" class="md-icon md-search__icon" data-md-component="reset" tabindex="-1">&#xE5CD;</button>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="result">
<div class="md-search-result__meta">
Type to start searching
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>

</div>
<div class="md-flex__cell md-flex__cell--shrink">
<div class="md-header-nav__source">
<a href="https://github.com/yangbajing/scala-web-development"
title="Go to repository"
class="md-source"
data-md-source="github">
<div class="md-source__icon">
<i class="fa fa-github"></i>
</div>
<div class="md-source__repository">
yangbajing/scala-web-development
</div>
</a>

</div>
</div>
</div>
</nav>
</header>

<div class="md-container">
<main class="md-main">
<div class="md-main__inner md-grid" data-md-component="container">
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" data-md-level="0" style="visibility: hidden">
<label class="md-nav__title md-nav__title--site" for="drawer">
<a href="../index.html" title="Scala Web 开发——基于Akka HTTP" class="md-nav__button md-logo">
<span class="md-nav__button md-logo">
<i class="md-icon">local_library</i>
</a>
<a href="../index.html" title="Scala Web 开发——基于Akka HTTP">
Scala Web 开发——基于Akka HTTP
</a>
</label>
<div class="md-nav__source">
<a href="https://github.com/yangbajing/scala-web-development"
title="Go to repository"
class="md-source"
data-md-source="github">
<div class="md-source__icon">
<i class="fa fa-github"></i>
</div>
<div class="md-source__repository">
yangbajing/scala-web-development
</div>
</a>

</div>
<ul>
  <li><a href="../preface.html" class="page">前言</a></li>
  <li><a href="../env/index.html" class="page">Scala 环境配置</a>
  <ul>
    <li><a href="../env/env.1.html" class="page">Sbt</a></li>
    <li><a href="../env/env.2.html" class="page">IDE开发工具</a></li>
    <li><a href="../env/env.z.html" class="page">小结</a></li>
  </ul></li>
  <li><a href="../scala/index.html" class="page">Scala 语言基础</a>
  <ul>
    <li><a href="../scala/scala.0.html" class="page">REPL</a></li>
    <li><a href="../scala/scala.1.html" class="page">你好，Scala</a></li>
    <li><a href="../scala/scala.2.html" class="page">Scala基础</a></li>
    <li><a href="../scala/scala.3.html" class="page">流程和函数</a></li>
    <li><a href="../scala/scala.4.html" class="page">集合</a></li>
    <li><a href="../scala/scala.5.html" class="page">class和object</a></li>
    <li><a href="../scala/scala.6.html" class="page">函数式</a></li>
    <li><a href="../scala/scala.7.html" class="page">Trait</a></li>
    <li><a href="../scala/scala.8.html" class="page">并发</a></li>
    <li><a href="../scala/scala.z.html" class="page">小结</a></li>
  </ul></li>
  <li><a href="../basic/index.html" class="page">Akka HTTP 基础</a>
  <ul>
    <li><a href="../basic/basic.0.html" class="page">Akka HTTP 基础</a></li>
    <li><a href="../basic/basic.1.html" class="page">Web 工作方式</a></li>
    <li><a href="../basic/basic.2.html" class="page">使用 Akka Http 搭建一个简单的 Web 服务</a></li>
    <li><a href="../basic/basic.3.html" class="page">Akka HTTP 的通用抽象</a></li>
    <li><a href="../basic/basic.z.html" class="page">小结</a></li>
  </ul></li>
  <li><a href="../server-api/index.html" class="page">服务端API</a>
  <ul>
    <li><a href="../server-api/work.html" class="page">Akka HTTP 如何使得 Web 工作</a></li>
    <li><a href="../server-api/advanced.html" class="page">高级服务端 API</a></li>
  </ul></li>
  <li><a href="../routing-dsl/index.html" class="page">路由DSL</a>
  <ul>
    <li><a href="../routing-dsl/route.html" class="page">Route 路由</a></li>
    <li><a href="../routing-dsl/directive.html" class="page">Directive 指令</a></li>
    <li><a href="../routing-dsl/custom-directive.html" class="page">自定义指令</a></li>
    <li><a href="../routing-dsl/rejections.html" class="page">拒绝 rejections</a></li>
    <li><a href="../routing-dsl/exception.html" class="page">异常处理</a></li>
    <li><a href="../routing-dsl/file-upload.html" class="page">实战：大文件断点上传、下载和秒传</a></li>
  </ul></li>
  <li><a href="../directives/index.html" class="page">常用指令</a>
  <ul>
    <li><a href="../directives/path.html" class="page">PathDirectives（路径指令）</a></li>
    <li><a href="../directives/method.html" class="page">directives/method.html</a></li>
    <li><a href="../directives/parameter_form.html" class="page">directives/parameter_form.html</a></li>
    <li><a href="../directives/marshalling.html" class="page">directives/marshalling.html</a></li>
    <li><a href="../directives/file.html" class="page">directives/file.html</a></li>
    <li><a href="../directives/cookie.html" class="page">directives/cookie.html</a></li>
  </ul></li>
  <li><a href="../data/index.html" class="page">数据</a>
  <ul>
    <li><a href="../data/data.0.html" class="page">数据</a></li>
    <li><a href="../data/data.1.html" class="page">JSON</a></li>
    <li><a href="../data/data.ant-design-pro.html" class="page">实战：为Ant Design Pro提供后端接口</a></li>
    <li><a href="../data/data.kryo.html" class="page">Kryo</a></li>
    <li><a href="../data/data.2.html" class="page">Protobuf</a></li>
    <li><a href="../data/data.z.html" class="page">小结</a></li>
  </ul></li>
  <li><a href="../test/index.html" class="page">测试</a>
  <ul>
    <li><a href="../test/test.0.html" class="page">测试</a></li>
    <li><a href="../test/test.1.html" class="page">Scalatest</a></li>
    <li><a href="../test/test.2.html" class="page">测试异步代码</a></li>
    <li><a href="../test/test.3.html" class="page">端到端测试Route</a></li>
    <li><a href="../test/test.z.html" class="page">小结</a></li>
  </ul></li>
  <li><a href="../actor/index.html" class="page">Akka Actor</a>
  <ul>
    <li><a href="../actor/actor.html" class="active page">Akka Typed Actor</a></li>
    <li><a href="../actor/actor-test.html" class="page">Akka Actor 测试</a></li>
    <li><a href="../actor/actor.z.html" class="page">Actor小结</a></li>
  </ul></li>
  <li><a href="../oauth/index.html" class="page">实战：实现OAuth 2服务</a>
  <ul>
    <li><a href="../oauth/oauth.0.html" class="page">实战：OAuth 2 服务</a></li>
    <li><a href="../oauth/oauth.1.html" class="page">OAuth 2简介</a></li>
    <li><a href="../oauth/oauth.2.html" class="page">OAuth 2接口设计</a></li>
    <li><a href="../oauth/oauth.3.html" class="page">OAuth 2服务实现</a></li>
    <li><a href="../oauth/oauth.z.html" class="page">小结</a></li>
  </ul></li>
  <li><a href="../database/index.html" class="page">访问数据库</a>
  <ul>
    <li><a href="../database/database.0.html" class="page">访问数据库</a></li>
    <li><a href="../database/database.1.html" class="page">使用 JDBC 访问 PostgreSQL</a></li>
    <li><a href="../database/database.2.html" class="page">使用 Slick 访问数据库</a></li>
    <li><a href="../database/database.3.html" class="page">访问 Cassandra 数据库</a></li>
    <li><a href="../database/database.4.html" class="page">访问 Redis</a></li>
    <li><a href="../database/database.5.html" class="page">访问 Elasticsearch</a></li>
    <li><a href="../database/database.z.html" class="page">小结</a></li>
  </ul></li>
  <li><a href="../engineering/index.html" class="page">工程化</a>
  <ul>
    <li><a href="../engineering/swagger.html" class="page">使用Swagger编写API文档</a></li>
    <li><a href="../engineering/guice.html" class="page">使用Guice管理类依赖</a></li>
  </ul></li>
  <li><a href="../grpc/index.html" class="page">Akka gRPC</a>
  <ul>
    <li><a href="../grpc/grpc.html" class="page">gRPC服务</a></li>
    <li><a href="../grpc/build-tool.html" class="page">构建工具</a></li>
    <li><a href="../grpc/deployment.html" class="page">部署</a></li>
    <li><a href="../grpc/grpc.z.html" class="page">小结</a></li>
  </ul></li>
  <li><a href="../config-discovery/index.html" class="page">实战：配置管理、服务发现系统</a></li>
  <li><a href="../appendix/index.html" class="page">附录</a>
  <ul>
    <li><a href="../appendix/appendix.0.html" class="page">参考资料</a></li>
    <li><a href="../appendix/appendix.1.html" class="page">专业术语</a></li>
    <li><a href="../appendix/appendix.2.html" class="page">词汇表</a></li>
  </ul></li>
  <li><a href="../donate.html" class="page">赞助</a></li>
</ul>
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="__toc">Table of contents</label>
<ul>
  <li><a href="../actor/actor.html#akka-typed-actor" class="header">Akka Typed Actor</a>
  <ul>
    <li><a href="../actor/actor.html#hello-scala-" class="header">Hello Scala!</a></li>
    <li><a href="../actor/actor.html#更复杂的一个示例" class="header">更复杂的一个示例</a></li>
    <li><a href="../actor/actor.html#在actorsystem-外部创建actor" class="header">在ActorSystem[_]外部创建actor</a></li>
    <li><a href="../actor/actor.html#小结" class="header">小结</a></li>
  </ul></li>
</ul>
</nav>

</nav>
<ul style="display: none">
<li class="md-nav__item md-version" id="project.version">
<label class="md-nav__link" for="__version">
<i class="md-icon" title="Version">label_outline</i> 1.0.0
</label>
</li>
</ul>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="__toc">Table of contents</label>
<ul>
  <li><a href="../actor/actor.html#akka-typed-actor" class="header">Akka Typed Actor</a>
  <ul>
    <li><a href="../actor/actor.html#hello-scala-" class="header">Hello Scala!</a></li>
    <li><a href="../actor/actor.html#更复杂的一个示例" class="header">更复杂的一个示例</a></li>
    <li><a href="../actor/actor.html#在actorsystem-外部创建actor" class="header">在ActorSystem[_]外部创建actor</a></li>
    <li><a href="../actor/actor.html#小结" class="header">小结</a></li>
  </ul></li>
</ul>
</nav>

</div>
</div>
</div>
<div class="md-content">
<article class="md-content__inner md-typeset">
<div class="md-content__searchable">
<h1><a href="#akka-typed-actor" name="akka-typed-actor" class="anchor"><span class="anchor-link"></span></a>Akka Typed Actor</h1>
<h2><a href="#hello-scala-" name="hello-scala-" class="anchor"><span class="anchor-link"></span></a>Hello Scala!</h2>
<p>Akka Typed Actor从2.4开始直到2.5可以商用，进而Akka 2.6已经把Akka Typed Actor做为推荐的Actor使用模式。Typed Actor与原先的Untyped Actor最大的区别Actor有类型了，其签名也改成了<code>akka.actor.typed.ActorRef[T]</code>。通过一个简单的示例来看看在Akka Typed环境下怎样使用Actor。</p>
<pre class="prettyprint"><a class="icon go-to-source" href="https://github.com/yangbajing/scala-web-development/tree/master/book/src/test/scala/book/typed/HelloScala.scala#L12-L28" target="_blank" title="Go to snippet source"></a><code class="language-scala">sealed trait Command
final case class Hello(message: String, replyTo: ActorRef[Reply]) extends Command
final case class Tell(message: String) extends Command

sealed trait Reply
final case class HelloReply(message: String) extends Reply

def apply(): Behavior[Command] = Behaviors.setup { context =&gt;
  Behaviors.receiveMessage {
    case Hello(message, replyTo) =&gt;
      replyTo ! HelloReply(s&quot;$message, scala!&quot;)
      Behaviors.same
    case Tell(message) =&gt;
      context.log.debug(&quot;收到消息：{}&quot;, message)
      Behaviors.same
  }
}</code></pre>
<p>Akka Typed不再需要通过类的形式来实现<code>Actor</code>接口定义，而是函数的形式来定义actor。可以看到，定义的actor类型为<code>Behavior[T]</code>（**形为**），通过<code>Behaviors.receiveMessage[T](T =&gt; Behavior[T]): Receive[T]</code>函数来处理接收到的消息，而<code>Receive</code>继承了<code>Behavior</code> trait。通过函数签名可以看到，每次接收到消息并对其处理完成后，都必需要返回一个新的形为。</p>
<p><code>apply(): Behavior[Command]</code>函数签名里的范性参数类型<code>Command</code>限制了这个actor将只接收<code>Command</code>或<code>Command</code>子类型的消息，编译器将在编译期对传给actor的消息做类型检查，相对于从前的untyped actor可以向actor传入任何类型的消息，这可以限制的减少程序中的bug。特别是在程序规模很大，当你定义了成百上千个消息时。</p>
<p>也因为有类型的actor，在Akka Typed中没有了隐式发送的<code>sender: ActorRef</code>，必需在发送的消息里面包含回复字段，就如<code>Hello</code>消息定义里的<code>replyTo: ActorRef[Reply]</code>字段一样。actor在处理完<code>Hello</code>消息后可以通过它向发送者回复处理结果。</p>
<pre class="prettyprint"><a class="icon go-to-source" href="https://github.com/yangbajing/scala-web-development/tree/master/book/src/test/scala/book/typed/HelloScala.scala#L37-L58" target="_blank" title="Go to snippet source"></a><code class="language-scala">&quot;tell&quot; in {
  val actorRef = spawn(HelloScala(), &quot;tell&quot;)
  actorRef ! Tell(&quot;Hello&quot;)
}

&quot;replyTo&quot; in {
  val actorRef = spawn(HelloScala(), &quot;replyTo&quot;)
  val probe = createTestProbe[Reply]()
  actorRef ! Hello(&quot;hello&quot;, probe.ref)
  probe.expectMessageType[HelloReply] should be(HelloReply(&quot;hello, scala!&quot;))
}

&quot;ask&quot; in {
  import akka.actor.typed.scaladsl.AskPattern._
  val actorRef = spawn(HelloScala(), &quot;ask&quot;)
  val reply =
    actorRef
      .ask[Reply](replyTo =&gt; Hello(&quot;Hello&quot;, replyTo))
      .mapTo[HelloReply]
      .futureValue
  reply.message should be(&quot;Hello, scala!&quot;)
}</code></pre>
<h2><a href="#更复杂的一个示例" name="更复杂的一个示例" class="anchor"><span class="anchor-link"></span></a>更复杂的一个示例</h2>
<p>上一个示例简单的演示了Akka Typed Actor的功能和基本使用方式，接下来看一个更复杂的示例，将展示Akka Typed更多的特性及功能。</p>
<p>首先是消息定义：</p>
<pre class="prettyprint"><code class="language-scala">  sealed trait Command
  trait ControlCommand extends Command { val clientId: String }
  trait ReplyCommand extends Command { val replyTo: ActorRef[Reply] }

  final case class Connect(clientId: String, replyTo: ActorRef[Reply]) extends ControlCommand with ReplyCommand
  final case class Disconnect(clientId: String, replyTo: ActorRef[Reply]) extends ControlCommand with ReplyCommand
  final case class QueryResource(clientId: String, replyTo: ActorRef[Reply]) extends ReplyCommand
  final private[typed] case object SessionTimeout extends Command
  final private case class ServiceKeyRegistered(registered: Receptionist.Registered) extends Command

  sealed trait Reply
  final case class Connected(status: Int, clientId: String) extends Reply
  final case class Disconnected(status: Int, clientId: String) extends Reply
  final case class ResourceQueried(status: Int, clientId: String, resources: Seq[String]) extends Reply
  final case class ReplyError(status: Int) extends Reply
</code></pre>
<p>上面分别定义了actor可接收的请求消息：<code>Command</code>和返回结果消息：<code>Reply</code>。建议对于需要返回值的消息使用：<code>replyTo</code>来命名收受返回值的actor字段，这里也可以不定义<code>Reply</code> trait来做为统一的返回值类型，可以直接返回结果类型，如：<code>ActorRef[String</code>。</p>
<p>这里将定义两个actor，一个做为父actor，一个做为子actor。父actor为：<code>ComplexActor</code>，管理连接客户端和转发消息到子actor，每次有新的客户端连接上来时做以客户端<code>clientId</code>做为名字创建一个子actor；子actor：<code>ComplexClient</code>，保持客户端连接会话，处理消息……</p>
<p><strong>ComplexActor</strong></p>
<pre class="prettyprint"><code class="language-scala">final class ComplexActor private(context: ActorContext[ComplexActor.Command]) {
  import ComplexActor._
  private var connects = Map.empty[String, ActorRef[Command]]

  def init(): Behavior[Command] = Behaviors.receiveMessage {
    case ServiceKeyRegistered(registered) if registered.isForKey(serviceKey) =&gt;
      context.log.info(&quot;Actor be registered, serviceKey: {}&quot;, serviceKey)
      receive()
    ....
  }

  def receive(): Behavior[Command] =
    Behaviors
      .receiveMessage[Command] {
        case cmd @ Connect(clientId, replyTo) =&gt;
          if (connects.contains(clientId)) {
            replyTo ! Connected(IntStatus.CONFLICT, clientId)
          } else {
            val child = context.spawn(
              Behaviors
                .supervise(ComplexClient(clientId))
                .onFailure(SupervisorStrategy.restart),
              clientId)
            context.watch(child)
            connects = connects.updated(clientId, child)
            child ! cmd
          }
          Behaviors.same
        ....
      }
      .receiveSignal {
        case (_, Terminated(child)) =&gt;
          val clientId = child.path.name
          connects -= clientId
          context.unwatch(child)
          Behaviors.same
      }
}
</code></pre>
<p><code>ComplexActor</code>在收到<code>Connect</code>消息后将首先判断请求客户端ID（<code>clientId</code>）是否已经连接，若重复连接将直接返回409错误（<code>Connected(IntStatus.CONFLICT, _)</code>）。若是一个新连接将调用<code>context.spawn</code>函数在创建一个字actor：<code>ComplexClient</code>。<code>spawn</code>函数签名如下：</p>
<pre class="prettyprint"><code class="language-scala">def spawn[U](behavior: Behavior[U], name: String, props: Props = Props.empty): ActorRef[U]
</code></pre>
<p><code>behavior</code>是要创建的actor，<code>name</code>为子actor的名字，需要保证在同一级内唯一（兄弟之间），<code>props</code>可对actor作一些自定义，如：线程执行器（<code>Dispatcher</code>）、邮箱等。</p>
<p><code>receiveSignal</code>用于接收系统控制信号消息，经典actor的<code>preRestart</code>和<code>postStop</code>回调函数（将分别做为<code>PreRestart</code>和<code>PostStop</code>信号），以及<code>Terminated</code>消息都将做为信号发送到这里。</p>
<p><strong>ComplexClient</strong></p>
<pre class="prettyprint"><code class="language-scala">final class ComplexClient private (
    clientId: String,
    context: ActorContext[ComplexActor.Command]) {
  import ComplexActor._

  def active(): Behavior[Command] = Behaviors.receiveMessagePartial {
    ....
    case SessionTimeout =&gt;
      context.log.warn(&quot;Inactive timeout, stop!&quot;)
      Behaviors.stopped
  }

  def init(): Behavior[Command] = Behaviors.receiveMessage {
    case Connect(`clientId`, replyTo) =&gt;
      replyTo ! Connected(IntStatus.OK, clientId)
      context.setReceiveTimeout(120.seconds, SessionTimeout)
      active()
    case other =&gt;
      context.log.warn(&quot;Receive invalid command: {}&quot;, other)
      Behaviors.same
  }
</code></pre>
<p><code>ComplexClient</code>定义了两个形为函数，<code>init()</code>和<code>active</code>。当客户端连接成功以后会返回<code>active()</code>函数作为actor新的形为来接收之后的消息。这种返回一个新的<code>Behavior</code>函数的形式替代了经典actor里的<code>become</code>、<code>unbecome</code>函数，它更直观，甚至还可以使用这种方式来实现**状态机**。</p>
<p><code>context.setReceiveTimeout(120.seconds, SessionTimeout)</code>用来设置两次消息接收之间的超时时间，这里设备为120秒。可以通过方式来实现服务端会话（session）超时判断，当session超时时返回<code>Behaviors.stopped</code>消息来停止actor（自己）。这里需要注意的是<code>context.stop</code>只能用来停止直接子actor，停止actor自身返回<code>stopped</code>形为即可，这与经典actor有着明显的区别。</p>
<h3><a href="#发现actor" name="发现actor" class="anchor"><span class="anchor-link"></span></a>发现actor</h3>
<p>Akka Typed取消了<code>actorSelection</code>函数，不再允许通过actor path路径来查找ActorRef。取而代之的是使用<code>Receptionist</code>机制来注册服务（actor实例）。也就是说，在Akka Typed中，actor默认情况下是不能查找的，只能通过引用（<code>ActorRef[T]</code>）来使用，要么actor之间具有父子关系，要么通过消息传递<code>ActorRef[T]</code>……</p>
<pre class="prettyprint"><code class="language-scala">object ComplexActor {
  val serviceKey = ServiceKey[Command](&quot;complex&quot;)

  def apply(): Behavior[Command] = Behaviors.setup { context =&gt;
    val registerAdapter = context.messageAdapter[Receptionist.Registered](value =&gt; ServiceKeyRegistered(value))
    context.system.receptionist ! Receptionist.Register(serviceKey, context.self, registerAdapter)
    new ComplexActor(context).init()
  }
}
</code></pre>
<p>上面代码通过<code>Receptionist.Register</code>将actor（<code>context.self</code>引用）以<code>serviceKey</code>注册到Actor系统的**receptionist**表，之后就可以通过<code>serviceKey</code>来发现并获取此actor的引用。</p>
<pre class="prettyprint"><code class="language-scala">  val actorRef: ActorRef[ComplexActor.Command] = system.receptionist
    .ask[Receptionist.Listing](Receptionist.Find(ComplexActor.serviceKey))
    .map { listing =&gt;
      if (listing.isForKey(serviceKey))
        listing.serviceInstances(serviceKey).head
      else 
        throw new IllegalAccessException(s&quot;Actor reference not found: $serviceKey&quot;)
    }
</code></pre>
<h3><a href="#消息适配器" name="消息适配器" class="anchor"><span class="anchor-link"></span></a>消息适配器</h3>
<p>有时候，需要将不匹配的消息发送给actor，比如：把receptionist服务注册结果 <code>Receptionist.Registered</code>发送给一个actor，我们可以通过将消息包装到一个实现了<code>Command</code> trait的case class来实现。如下面的代码示例：</p>
<pre class="prettyprint"><code class="language-scala">val registerAdapter: ActorRef[Receptionist.Registered] =
  context.messageAdapter[Receptionist.Registered](value =&gt; ServiceKeyRegistered(value))
</code></pre>
<p>在使用<code>Receptionist.Register</code>时将<code>registerAdapter</code>作为第3个参数传入，这样服务注册结果就将被包装成<code>ServiceKeyRegistered</code>消息传给actor。</p>
<h3><a href="#在actor内部处理异步任务" name="在actor内部处理异步任务" class="anchor"><span class="anchor-link"></span></a>在actor内部处理异步任务</h3>
<p>actor内部消息都是串行执行的，在actor内执行异步操作时需要小心。不能在<code>Future</code>的回调函数里直接操作actor内部变量，因为它们很可能在两个不同的线程中。</p>
<p>可以通过<code>context.pipeToSelf</code>将异步结果转换成一个消息传递给actor，这样异步结果将进入actor的邮箱列队，通过正确的消息处理机制来处理。</p>
<pre class="prettyprint"><code class="language-scala">  case QueryResource(_, replyTo) =&gt;
    context.pipeToSelf(findExternalResource())(value =&gt; InternalQueryResource(value, replyTo))
    Behaviors.same

  case InternalQueryResource(tryValue, replyTo) =&gt;
    replyTo ! tryValue
      .map(ResourceQueried(IntStatus.OK, clientId, _))
      .getOrElse(ResourceQueried(IntStatus.INTERNAL_ERROR, clientId, Nil))
    Behaviors.same
</code></pre>
<h2><a href="#在actorsystem-外部创建actor" name="在actorsystem-外部创建actor" class="anchor"><span class="anchor-link"></span></a>在ActorSystem[_]外部创建actor</h2>
<p>Akka Typed开始，<code>ActorSystem[T]</code>也拥有一个泛型参数，在构造ActorSystem时需要传入一个默认<code>Behavior[T]</code>，并将其作为经典actor下的user守卫（也就类似拥有<code>akka://system-name/user</code>这个路径的actor），同时<code>ActorSystem[T]</code>的<code>actorOf</code>函数也被取消。Akka Typed推荐应用都从传给ActorSystem的默认<code>Behavior[T]</code>开始构建actor树。但有时，也许通过<code>ActorSystem[T]</code>的实例来创建actor是有意义的，可以通过将typed的<code>ActorSystem[T]</code>转换成经典的untyped <code>ActorSystem</code>来实现。代码如下：</p>
<pre class="prettyprint"><code class="language-scala">implicit val timeout = Timeout(2.seconds)
implicit val system: ActorSystem[_] = _ // ....

val spawnActor: ActorRef[SpawnProtocol.Command] = system.toClassic
  .actorOf(
    PropsAdapter(Behaviors.supervise(SpawnProtocol())
      .onFailure(SupervisorStrategy.resume)), &quot;spawn&quot;)
  .toTyped[SpawnProtocol.Command]

val helloScalaF: Future[ActorRef[HelloScala.Command]] = 
  spawnActor.ask[ActorRef[HelloScala.Command]](replyTo =&gt;
    SpawnProtocol.Spawn(HelloScala(), &quot;sample&quot;, Props.empty, replyTo))

val helloScala: ActorRef[HelloScala.Command] = Await.result(helloScalaF, 2.seconds)
</code></pre>
<p>也可以将<code>SpawnProtocol()</code>作为<code>ActorSystem[_]</code>的初始<code>Behavior[T]</code>来构造ActorSystem，这样就可以通过<code>system.ask[ActorRef[T]](SpawnProtocol.Spawn(....))</code>来创建在user守卫下的actor了。</p>
<h2><a href="#小结" name="小结" class="anchor"><span class="anchor-link"></span></a>小结</h2>
<p>本文通过两个例子展示了Akka Typed的特性，它与经典actor的区别还是挺大的。从untyped和typed，actor拥有了类型，这对于大规模actor系统开发可以在编译期发现很多重复，它将强制你在设计actor时首先考虑消息的定义。定义的消息即是actor之间的数据交互协议，消息定义的过程也是业务模式和模块划分的过程。</p>
<p><strong>完整示例代码</strong></p>
<p><strong>HelloScala.scala</strong></p>
<pre class="prettyprint"><a class="icon go-to-source" href="https://github.com/yangbajing/scala-web-development/tree/master/book/src/test/scala/book/typed/HelloScala.scala#L10-L61" target="_blank" title="Go to snippet source"></a><code class="language-scala">object HelloScala {
  sealed trait Command
  final case class Hello(message: String, replyTo: ActorRef[Reply]) extends Command
  final case class Tell(message: String) extends Command

  sealed trait Reply
  final case class HelloReply(message: String) extends Reply

  def apply(): Behavior[Command] = Behaviors.setup { context =&gt;
    Behaviors.receiveMessage {
      case Hello(message, replyTo) =&gt;
        replyTo ! HelloReply(s&quot;$message, scala!&quot;)
        Behaviors.same
      case Tell(message) =&gt;
        context.log.debug(&quot;收到消息：{}&quot;, message)
        Behaviors.same
    }
  }
}

class HelloScalaSpec extends ScalaTestWithActorTestKit with WordSpecLike {
  import HelloScala._

  &quot;HelloScala&quot; should {
    &quot;tell&quot; in {
      val actorRef = spawn(HelloScala(), &quot;tell&quot;)
      actorRef ! Tell(&quot;Hello&quot;)
    }

    &quot;replyTo&quot; in {
      val actorRef = spawn(HelloScala(), &quot;replyTo&quot;)
      val probe = createTestProbe[Reply]()
      actorRef ! Hello(&quot;hello&quot;, probe.ref)
      probe.expectMessageType[HelloReply] should be(HelloReply(&quot;hello, scala!&quot;))
    }

    &quot;ask&quot; in {
      import akka.actor.typed.scaladsl.AskPattern._
      val actorRef = spawn(HelloScala(), &quot;ask&quot;)
      val reply =
        actorRef
          .ask[Reply](replyTo =&gt; Hello(&quot;Hello&quot;, replyTo))
          .mapTo[HelloReply]
          .futureValue
      reply.message should be(&quot;Hello, scala!&quot;)
    }
  }
}</code></pre>
<p><strong>ComplexActor.scala</strong></p>
<pre class="prettyprint"><a class="icon go-to-source" href="https://github.com/yangbajing/scala-web-development/tree/master/book/src/test/scala/book/typed/ComplexActor.scala#L32-L331" target="_blank" title="Go to snippet source"></a><code class="language-scala">object ComplexActor {
  sealed trait Command
  trait ControlCommand extends Command { val clientId: String }
  trait ReplyCommand extends Command { val replyTo: ActorRef[Reply] }

  final case class Connect(clientId: String, replyTo: ActorRef[Reply])
      extends ControlCommand
      with ReplyCommand
  final case class Disconnect(clientId: String, replyTo: ActorRef[Reply])
      extends ControlCommand
      with ReplyCommand
  final case class AskMessage(
      clientId: String,
      message: String,
      replyTo: ActorRef[Reply])
      extends ReplyCommand
  final case class ConnectCount(replyTo: ActorRef[Reply]) extends ReplyCommand
  final case class QueryResource(clientId: String, replyTo: ActorRef[Reply])
      extends ReplyCommand
  final case class PublishEvent(clientId: String, event: String, payload: String)
      extends Command
  final private[typed] case object SessionTimeout extends Command
  final private case class ServiceKeyRegistered(
      registered: Receptionist.Registered)
      extends Command

  sealed trait Reply
  final case class Connected(status: Int, clientId: String) extends Reply
  final case class Disconnected(status: Int, clientId: String) extends Reply
  final case class MessageAsked(status: Int, clientId: String, reply: String)
      extends Reply
  final case class ConnectCounted(count: Int, status: Int = IntStatus.OK)
      extends Reply
  final case class ResourceQueried(
      status: Int,
      clientId: String,
      resources: Seq[String])
      extends Reply
  final case class ReplyError(status: Int) extends Reply

  val serviceKey = ServiceKey[Command](&quot;complex&quot;)

  def apply(): Behavior[Command] = Behaviors.setup { context =&gt;
    val registerAdapter =
      context.messageAdapter[Receptionist.Registered](value =&gt;
        ServiceKeyRegistered(value))
    context.system.receptionist ! Receptionist
      .Register(serviceKey, context.self, registerAdapter)
    new ComplexActor(context).init()
  }
}

final class ComplexActor(context: ActorContext[ComplexActor.Command]) {
  import ComplexActor._
  private var connects = Map.empty[String, ActorRef[Command]]

  def init(): Receive[Command] =
    Behaviors.receiveMessage[Command] {
      case ServiceKeyRegistered(registered) if registered.isForKey(serviceKey) =&gt;
        context.log.info(&quot;Actor be registered, serviceKey: {}&quot;, serviceKey)
        receive()
      case cmd: ReplyCommand =&gt;
        cmd.replyTo ! ReplyError(IntStatus.SERVICE_UNAVAILABLE)
        Behaviors.same
      case other =&gt;
        context.log
          .warn(&quot;Actor not registered, receive invalid message: {}&quot;, other)
        Behaviors.same
    }

  def receive(): Behavior[Command] =
    Behaviors
      .receiveMessagePartial[Command] {
        case cmd @ Connect(clientId, replyTo) =&gt;
          if (connects.contains(clientId)) {
            replyTo ! Connected(IntStatus.CONFLICT, clientId)
          } else {
            val child = context.spawn(
              Behaviors
                .supervise(
                  ComplexClient(clientId, context.self.narrow[ControlCommand]))
                .onFailure(SupervisorStrategy.restart),
              clientId)
            context.watch(child)
            connects = connects.updated(clientId, child)
            child ! cmd
          }
          Behaviors.same

        case cmd @ Disconnect(clientId, replyTo) =&gt;
          if (connects.contains(clientId)) {
            connects(clientId) ! cmd
          } else {
            replyTo ! Disconnected(IntStatus.NOT_FOUND, clientId)
          }
          Behaviors.same

        case cmd: AskMessage =&gt;
          connects.get(cmd.clientId) match {
            case Some(ref) =&gt; ref ! cmd
            case None      =&gt; cmd.replyTo ! ReplyError(IntStatus.NOT_FOUND)
          }
          Behaviors.same

        case event: PublishEvent =&gt;
          connects.get(event.clientId).foreach(_ ! event)
          Behaviors.same

        case ConnectCount(replyTo) =&gt;
          replyTo ! ConnectCounted(connects.size)
          Behaviors.same

        case cmd: QueryResource =&gt;
          connects.get(cmd.clientId) match {
            case Some(ref) =&gt; ref ! cmd
            case None      =&gt; cmd.replyTo ! ReplyError(IntStatus.NOT_FOUND)
          }
          Behaviors.same
      }
      .receiveSignal {
        case (_, Terminated(child)) =&gt;
          val clientId = child.path.name
          connects -= clientId
          context.unwatch(child)
          Behaviors.same
      }
}

object ComplexClient {
  import ComplexActor._

  final private[typed] case class InternalQueryResource(
      resources: Try[Seq[String]],
      replyTo: ActorRef[Reply])
      extends Command

  def apply(
      clientId: String,
      parent: ActorRef[ControlCommand]): Behavior[Command] =
    Behaviors.setup { context =&gt;
      Behaviors.withTimers(timers =&gt;
        new ComplexClient(clientId, parent, timers, context).init())
    }
}

final class ComplexClient private (
    clientId: String,
    parent: ActorRef[ComplexActor.ControlCommand],
    timers: TimerScheduler[ComplexActor.Command],
    context: ActorContext[ComplexActor.Command]) {
  import ComplexActor._
  import ComplexClient._
  import context.executionContext

  def active(): Behavior[Command] = Behaviors.receiveMessagePartial {
    case AskMessage(_, message, reply) =&gt;
      reply ! MessageAsked(IntStatus.OK, clientId, message.reverse)
      Behaviors.same

    case PublishEvent(_, event, payload) =&gt;
      context.log.debug(&quot;Receive event: {}, payload: {}&quot;, event, payload)
      Behaviors.same

    case QueryResource(_, replyTo) =&gt;
      context.pipeToSelf(findExternalResource())(value =&gt;
        InternalQueryResource(value, replyTo))
      Behaviors.same

    case InternalQueryResource(tryValue, replyTo) =&gt;
      replyTo ! tryValue
        .map(ResourceQueried(IntStatus.OK, clientId, _))
        .getOrElse(ResourceQueried(IntStatus.INTERNAL_ERROR, clientId, Nil))
      Behaviors.same

    case Disconnect(_, replyTo) =&gt;
      replyTo ! Disconnected(IntStatus.OK, clientId)
      Behaviors.stopped

    case SessionTimeout =&gt;
      context.log.warn(&quot;Inactive timeout, stop!&quot;)
      Behaviors.stopped
  }

  def init(): Behavior[Command] = Behaviors.receiveMessage {
    case Connect(`clientId`, replyTo) =&gt;
      replyTo ! Connected(IntStatus.OK, clientId)
      context.setReceiveTimeout(120.seconds, SessionTimeout)
      active()
    case other =&gt;
      context.log.warn(&quot;Receive invalid command: {}&quot;, other)
      Behaviors.same
  }

  private def findExternalResource(): Future[Seq[String]] = Future {
    TimeUnit.MILLISECONDS.sleep(10)
    Range(0, 10).map(_.toString).toVector
  }
}

class ComplexActorSpec
    extends WordSpec
    with Matchers
    with ScalaFutures
    with OptionValues
    with BeforeAndAfterAll
    with StrictLogging {
  implicit private val system = ActorSystem(SpawnProtocol(), &quot;complex-manager&quot;)
  implicit private val timeout = Timeout(2.seconds)

  implicit override def patienceConfig: PatienceConfig =
    PatienceConfig(Span(10, Seconds), Span(50, Millis))

  &quot;ComplexActor&quot; should {
    var complexActor: ActorRef[ComplexActor.Command] = null

    &quot;Create actor from outside of ActorSystem[_]&quot; in {
      complexActor = system
        .ask[ActorRef[ComplexActor.Command]](replTo =&gt;
          SpawnProtocol.Spawn(ComplexActor(), &quot;complex&quot;, Props.empty, replTo))
        .futureValue
      complexActor.path.name shouldBe &quot;complex&quot;
    }

    &quot;Discover actors using ServiceKey[T]&quot; in {
      val maybeDeepActor =
        AkkaUtils
          .findActorByServiceKey(ComplexActor.serviceKey, 500.millis)
          .futureValue
      val ref = maybeDeepActor.value
      ref shouldBe complexActor
    }

    val client1 = &quot;client1&quot;

    &quot;Connect&quot; in {
      val connected =
        complexActor
          .ask[ComplexActor.Reply](ComplexActor.Connect(client1, _))
          .mapTo[ComplexActor.Connected]
          .futureValue
      connected.status should be(IntStatus.OK)
      connected.clientId should be(client1)

      val connectCounted =
        complexActor
          .ask[ComplexActor.Reply](ComplexActor.ConnectCount)
          .mapTo[ComplexActor.ConnectCounted]
          .futureValue
      connectCounted.count should be &gt; 0
    }

    &quot;AskMessage&quot; in {
      val messageAsked = complexActor
        .ask[ComplexActor.Reply](ComplexActor.AskMessage(client1, &quot;hello&quot;, _))
        .mapTo[ComplexActor.MessageAsked]
        .futureValue
      messageAsked should be(
        ComplexActor.MessageAsked(IntStatus.OK, client1, &quot;olleh&quot;))
    }

    &quot;QueryResource&quot; in {
      val queried = complexActor
        .ask[ComplexActor.Reply](ComplexActor.QueryResource(client1, _))
        .mapTo[ComplexActor.ResourceQueried]
        .futureValue
      queried.status should be(IntStatus.OK)
      queried.resources should contain(&quot;3&quot;)
    }

    &quot;Disconnect&quot; in {
      val disconnected = complexActor
        .ask[ComplexActor.Reply](replyTo =&gt;
          ComplexActor.Disconnect(client1, replyTo))
        .mapTo[ComplexActor.Disconnected]
        .futureValue
      disconnected.status should be(IntStatus.OK)
      disconnected.clientId should be(client1)

      val connectCounted =
        complexActor
          .ask[ComplexActor.Reply](ComplexActor.ConnectCount)
          .mapTo[ComplexActor.ConnectCounted]
          .futureValue
      connectCounted.count should be(0)
    }

    &quot;AskMessage return 404&quot; in {
      val messageAsked = complexActor
        .ask[ComplexActor.Reply](ComplexActor.AskMessage(client1, &quot;hello&quot;, _))
        .mapTo[ComplexActor.ReplyError]
        .futureValue
      messageAsked.status should be(IntStatus.NOT_FOUND)
    }
  }

  override protected def afterAll(): Unit = {
    system.terminate()
    Await.result(system.whenTerminated, 10.seconds)
  }
}</code></pre>
</div>
<div>
<a href="https://github.com/yangbajing/scala-web-development/tree/master/book/src/main/paradox/actor/actor.md" title="Edit this page" class="md-source-file md-edit">
Edit this page
</a>
</div>
<div class="print-only">
<span class="md-source-file md-version">
1.0.0
</span>
</div>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
<a href="../actor/index.html" title="Akka Actor" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
</div>
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Previous
</span>
Akka Actor
</span>
</div>
</a>
<a href="../actor/actor-test.html" title="Akka Actor 测试" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Next
</span>
Akka Actor 测试
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>
</div>
</a>
</nav>
</div>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
Powered by
<a href="https://github.com/lightbend/paradox">Paradox</a>
and
<a href="https://jonas.github.io/paradox-material-theme/">Paradox Material Theme</a>

</div>
<div class="md-footer-social">
<a href="https://github.com/yangbajing" class="md-footer-social__link fa fa-github"></a><a href="https://weibo.com/yangbajing" class="md-footer-social__link fa fa-globe"></a><a href="https://www.yangbajing.me/" class="md-footer-social__link fa fa-globe"></a>
</div>

</div>
</div>
</footer>

</div>
<script src="../assets/javascripts/application.583bbe55.js"></script>
<script src="../assets/javascripts/paradox-material-theme.js"></script>
<script>app.initialize({version:"0.17",url:{base:"../."}})</script>
<script type="text/javascript" src="../lib/prettify/prettify.js"></script>
<script type="text/javascript" src="../lib/prettify/lang-scala.js"></script>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function(event) {
window.prettyPrint && prettyPrint();
});
</script>
</body>
</html>
